package com.example.dailyrecord.util;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StreamUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.concurrent.*;

/**
 * 各种乱七八糟的功能
 */
public class FunctionUtils {

    private static ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    public static final String FORMAT_YYYYMMDDHHMMSS = "yyyy-MM-dd HH:mm:ss";
    public static final String FORMAT_YYYYMMDD = "yyyy-MM-dd";
    public static final String FORMAT_YYYYMM = "yyyy-MM";
    public static final String FORMAT_YYYY = "yyyy";
    public static final String FORMAT_MMDD = "MM-dd";
    public static final String FORMAT_HHMMSS = "HH:mm:ss";
    public static final DateTimeFormatter YYYYMMDDHHMMSS = DateTimeFormatter.ofPattern(FORMAT_YYYYMMDDHHMMSS);
    public static final DateTimeFormatter YYYYMMDD = DateTimeFormatter.ofPattern(FORMAT_YYYYMMDD);
    public static final DateTimeFormatter YYYYMM = DateTimeFormatter.ofPattern(FORMAT_YYYYMM);
    public static final DateTimeFormatter MMDD = DateTimeFormatter.ofPattern(FORMAT_MMDD);
    public static final DateTimeFormatter YYYY = DateTimeFormatter.ofPattern(FORMAT_YYYY);
    public static final DateTimeFormatter HHMMSS = DateTimeFormatter.ofPattern(FORMAT_HHMMSS);
    public static final SimpleDateFormat DATE_YYYYMMDDHHMMSS = new SimpleDateFormat(FORMAT_YYYYMMDDHHMMSS);

    public static final DecimalFormat DF1 = new DecimalFormat("##.#");
    public static final DecimalFormat DF2 = new DecimalFormat("##.##");
    public static final DecimalFormat DF3 = new DecimalFormat("##.###");
    public static final DecimalFormat DF4 = new DecimalFormat("##.####");
    public static final String SEMICOLON = ";";
    public static final String COMMA = ",";
    public static final String UNDERLINE = "_";
    public static final String POINT = ".";
    public static final String SPACE = " ";
    public static final String L_PARENTHESE = "(";
    public static final String R_PARENTHESE = ")";
    public static final String L_BRACE = "{";
    public static final String R_BRACE = "}";
    public static final String Percent = "%";
    public static final String VERTICAL = "|";
    public static final String SINGLE_QUOTATION_MARK = "\'";
    public static final String DOUBLE_QUOTATION_MARK = "\"";

    public static String dataFormat(Date date) {
        return DATE_YYYYMMDDHHMMSS.format(date);
    }

    public static byte[] toByteArray(InputStream inputStream) throws IOException {
        byte[] buffer = new byte[1024 * 4];
        int len;
        while (-1 != (len = inputStream.read(buffer))) {
            byteArrayOutputStream.write(buffer, 0, len);
        }
        return byteArrayOutputStream.toByteArray();
    }

    public static String getNowDate() {
        return getNowTime(YYYYMMDD);
    }

    public static String getNowTime() {
        return getNowTime(YYYYMMDDHHMMSS);
    }

    public static String getNowTime(DateTimeFormatter dateFormater) {
        return LocalDateTime.now().format(dateFormater);
    }

    public static Date localDate2Date(LocalDate localDate) {
        if (null == localDate) {
            return null;
        }
        ZonedDateTime zonedDateTime = localDate.atStartOfDay(ZoneId.systemDefault());
        return Date.from(zonedDateTime.toInstant());
    }

    public static LocalDateTime parse(String time) {
        return parse(time, YYYYMMDDHHMMSS);
    }

    public static LocalDateTime parse(String time, DateTimeFormatter dateFormater) {
        return LocalDateTime.parse(time, dateFormater);
    }

    /**
     * @param number 要转化的数
     * @param length 小数位数
     * @return
     */
    public static String getDecimal(Number number, int length) {
        String res = null;
        switch (length) {
            case 1:
                res = DF1.format(number);
                break;
            case 2:
                res = DF2.format(number);
                break;
            case 3:
                res = DF3.format(number);
                break;
            case 4:
                res = DF4.format(number);
                break;
        }
        return res;
    }

    public static float getCompareBl(float n1, float n2) {
        if (n2 == 0) {
            return 0;
        }
        float res = (n1 - n2) / n2;
        String s = getDecimal(res, 2);
        return Float.parseFloat(s);
    }

    public static float getBl(int n1, int n2) {
        if (n2 == 0) {
            return 0;
        }
        float res = (float) (n1) / n2;
        String s = getDecimal(res, 2);
        return Float.parseFloat(s);
    }

    public static Date formatDate(String time) {
        Date date = null;
        try {
            date = DATE_YYYYMMDDHHMMSS.parse(time);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return date;
    }

    /**
     * 字符串转List<String>
     *
     * @param target
     * @param split
     * @return
     */
    public static List<String> getListByString(String target, String split) {
        if (StringUtils.isEmpty(split)) {
            String[] res = {target};
            return new ArrayList<>(Arrays.asList(res));
        } else {
            return new ArrayList<>(Arrays.asList(target.split(split)));
        }
    }

    public static void closeQuietly(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    public static void downloadByStream(HttpServletResponse response, InputStream inputStream, String filename) {
        try {
            byte[] data = StreamUtils.copyToByteArray(inputStream);
            downloadByByte(response, data, filename);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void downloadByByte(HttpServletResponse response, byte[] data, String filename) {
        try {
            response.setContentType("application/force-download");
            response.setContentType("multipart/form-data;charset=UTF-8");
            response.addHeader("Content-Disposition", "attachment;fileName=" +
                    new String(filename.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
            response.getOutputStream().write(data);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void downloadByFile(HttpServletResponse response, File file) {
        try {
            downloadByStream(response, new FileInputStream(file), file.getName());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }


    public static void downloadTemplate(HttpServletResponse response, String... path) throws FileNotFoundException {
        downloadByFile(response, getResourceFile(path));
    }

    public static File getResourceFile(String... path) throws FileNotFoundException {
        return ResourceUtils.getFile("classpath:" + FileUtils.getPath(path));
    }

    /**
     * 以ResponseEntity<byte[]>形式下载
     *
     * @param file
     * @return
     * @throws IOException
     */
    public static ResponseEntity<byte[]> downloadByResponseEntity(File file) throws IOException {
        return downloadByResponseEntity(new FileInputStream(file), file.getName());
    }

    public static ResponseEntity<byte[]> downloadByResponseEntity(InputStream inputStream, String filename) throws IOException {
        // 构建响应
        ResponseEntity.BodyBuilder bodyBuilder = ResponseEntity.ok();
        bodyBuilder.contentLength(inputStream.available());
        bodyBuilder.contentType(MediaType.APPLICATION_OCTET_STREAM);
        bodyBuilder.header("Content-Disposition", "attachment;filename=" +
                new String(filename.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
        return bodyBuilder.body(toByteArray(inputStream));
    }

    /**
     * 获取当前请求来的IP地址
     *
     * @param request
     * @return
     * @throws UnknownHostException
     */
    public static String getIP(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
            if (ip.equals("127.0.0.1")) {
                // 根据网卡取本机配置的IP
                try {
                    ip = InetAddress.getLocalHost().getHostAddress();
                } catch (UnknownHostException e) {
                    e.printStackTrace();
                }
            }
        }
        // 对于通过多个代理的情况，第一个IP为客户端真实IP,多个IP按照','分割
        if (ip != null && ip.length() > 15) {
            // "***.***.***.***".length() = 15
            if (ip.indexOf(",") > 0) {
                ip = ip.substring(0, ip.indexOf(","));
            }
        }
        return ip;

    }

    /**
     * 从指定from的下标开始
     *
     * @param list
     * @param from
     * @return
     */
    public static int findForm(List<String> list, String from) {
        int index = 0;
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).equalsIgnoreCase(from)) {
                return i;
            }
        }
        return index;
    }

    public static <T> int findForm(List<T> list, String fName, String from) {
        int index = 0;
        for (int i = 0; i < list.size(); i++) {
            if (String.valueOf(getObjectValue(list.get(i), fName)).equalsIgnoreCase(from)) {
                return i;
            }
        }
        return index;
    }

    public static <T> List<String> getObjectValue(T t) {
        Field[] fields = t.getClass().getDeclaredFields();
        List<String> res = new ArrayList<>(fields.length);
        for (Field field : fields) {
            res.add(String.valueOf(getObjectValue(t, field.getName())));
        }
        return res;
    }

    public static <T> Object getObjectValue(T t, String name) {
        Object o = null;
        try {
            o = FieldUtils.readField(t, name, true);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return o;
    }

    public static void sleep(long duration) {
        try {
            Thread.sleep(duration);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static String getBeforeSuffix(String target, String split, boolean contains) {
        if (target.contains(split)) {
            int pos = target.lastIndexOf(split);
            pos = contains ? pos + 1 : pos;
            return target.substring(0, pos);
        }
        return target;
    }

    public static String getBetweenThisAndThat(String target, String leftStr, String rightStr) {
        int left = StringUtils.indexOf(target, leftStr) + 1;
        int right = StringUtils.indexOf(target, rightStr);
        return target.substring(left, right);
    }

    public static <T> boolean isEmpty(List<T> list) {
        return list == null || list.size() == 0;
    }

    public static <T> boolean isNotEmpty(List<T> list) {
        return !isEmpty(list);
    }

    public static Method getMethod(Object o, String mName, Class<?>... classes) {
        Method method = null;
        try {
            method = o.getClass().getMethod(mName, classes);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return method;
    }

    public static <T> List<T> toList(T... ts) {
        List<T> list;
        if (ts.length > 0) {
            list = new ArrayList<>(Arrays.asList(ts));
        } else {
            list = new ArrayList<>();
        }
        return list;
    }

    /**
     * 将p转成t
     *
     * @param t      目标对象
     * @param p      数据来源对象
     * @param indexs 下标数组
     * @param <P>    来源类型
     * @param <T>    目标类型
     */
    public static <P, T> T trans(T t, P p, byte[] indexs) {
        Field[] tFields = FieldUtils.getAllFields(t.getClass());
        Field[] pFields = FieldUtils.getAllFields(p.getClass());
        for (int i = 0; i < indexs.length; i++) {
            if (indexs[i] == -1) {
                continue;
            }
            try {
                Object o = FieldUtils.readField(pFields[indexs[i]], p);
                FieldUtils.writeField(tFields[i], t, o);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return t;
    }

    public static String encode(String s) {
        String result = null;
        try {
            result = URLEncoder.encode(s, Charset.defaultCharset().toString());
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return result;
    }

    public static String decode(String s) {
        String result = null;
        try {
            result = URLDecoder.decode(s, Charset.defaultCharset().toString());
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return result;
    }

    private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
            10, 20, 1, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(20), new ThreadPoolExecutor.CallerRunsPolicy());

    public static ResponseEntity<byte[]> addMission(Callable<ResponseEntity<byte[]>> callable)
            throws ExecutionException, InterruptedException {
        return THREAD_POOL_EXECUTOR.submit(callable).get();
    }

    public static void addMission(Runnable runnable) {
        THREAD_POOL_EXECUTOR.execute(runnable);
    }

    public static <T extends Comparable<T>> boolean between(T t, T from, boolean leq, T to, boolean req) {
        if (t == null) {
            return false;
        }
        return (leq ? t.compareTo(from) >= 0 : t.compareTo(from) > 0) && (req ? t.compareTo(to) <= 0 : t.compareTo(to) < 0);
    }

    public static <T extends Comparable<T>> boolean lessThen(T left, T right, boolean leq) {
        if (left == null) {
            return true;
        }
        if (right == null) {
            return false;
        }
        return leq ? left.compareTo(right) <= 0 : left.compareTo(right) < 0;
    }

    public static <T extends Comparable<T>> boolean greaterThen(T left, T right, boolean leq) {
        if (left == null) {
            return false;
        }
        if (right == null) {
            return true;
        }
        return leq ? left.compareTo(right) >= 0 : left.compareTo(right) > 0;
    }

    public static InputStream getResource(String path) {
        ClassPathResource classPathResource = new ClassPathResource(path);
        InputStream inputStream = null;
        try {
            inputStream = classPathResource.getInputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return inputStream;
    }

    public static boolean equalsAny(String target, String... choice) {
        for (String s : choice) {
            if (StringUtils.equals(target, s)) {
                return true;
            }
        }
        return false;
    }

    public static boolean equalsIgnoreCaseAny(String target, String... choice) {
        for (String s : choice) {
            if (StringUtils.equalsIgnoreCase(target, s)) {
                return true;
            }
        }
        return false;
    }

    public static boolean equalsEvery(String target, String... choice) {
        for (String s : choice) {
            if (!StringUtils.equals(target, s)) {
                return false;
            }
        }
        return true;
    }

    public static boolean equalsIgnoreCaseEvery(String target, String... choice) {
        for (String s : choice) {
            if (!StringUtils.equalsIgnoreCase(target, s)) {
                return false;
            }
        }
        return true;
    }

    public static boolean containsAny(String target, String... choice) {
        return StringUtils.containsAny(target, choice);
    }

    public static boolean containsIgnoreCaseAny(String target, String... choice) {
        for (String s : choice) {
            if (StringUtils.containsIgnoreCase(target, s)) {
                return true;
            }
        }
        return false;
    }

    public static boolean containsEvery(String target, String... choice) {
        for (String s : choice) {
            if (!StringUtils.contains(target, s)) {
                return false;
            }
        }
        return true;
    }

    public static boolean containsIgnoreEvery(String target, String... choice) {
        for (String s : choice) {
            if (!StringUtils.containsIgnoreCase(target, s)) {
                return false;
            }
        }
        return true;
    }

    public static boolean endwithAny(String target, String... choice) {
        return StringUtils.endsWithAny(target, choice);
    }

    public static boolean endwithIgnoreCaseAny(String target, String... choice) {
        for (String s : choice) {
            if (StringUtils.endsWithIgnoreCase(target, s)) {
                return true;
            }
        }
        return false;
    }

    public static boolean endwithEvery(String target, String... choice) {
        for (String s : choice) {
            if (!StringUtils.endsWith(target, s)) {
                return false;
            }
        }
        return true;
    }

    public static boolean endwithIgnoreCaseEvery(String target, String... choice) {
        for (String s : choice) {
            if (!StringUtils.endsWithIgnoreCase(target, s)) {
                return false;
            }
        }
        return true;
    }

    public static <N, P> Map<N, P> toMap(List<P> list, String fieldName) {
        Map<N, P> map = new LinkedHashMap<>(list.size());
        try {
            for (P p : list) {
                Field field;
                field = p.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);
                N n = (N) field.get(p);
                map.put(n, p);
            }
        } catch (ReflectiveOperationException e) {
            e.printStackTrace();
        }
        return map;
    }

    public static String getByteSize(long size) {
        if (size < 1024) {
            return size + "B";
        } else {
            size = size / 1024;
        }
        if (size < 1024) {
            return size + "KB";
        } else {
            size = size / 1024;
        }
        if (size < 1024) {
            size = size * 100;
            return (size / 100) + "."
                    + (size % 100) + "MB";
        } else {
            size = size * 100 / 1024;
            return (size / 100) + "."
                    + (size % 100) + "GB";
        }
    }

    public static int countMatches(String str, String sub) {
        if (StringUtils.isEmpty(str) || StringUtils.isEmpty(sub)) {
            return 0;
        }
        int count = 0;
        for (int idx = 0; (idx = str.indexOf(sub, idx)) != -1; idx += sub.length()) {
            ++count;
        }
        return count;
    }

    public static int countMatchesLowerCaseIgnore(String str, String sub) {
        if (StringUtils.isEmpty(str) || StringUtils.isEmpty(sub)) {
            return 0;
        }
        return countMatches(str.toLowerCase(), sub.toLowerCase());
    }

    public static LocalDateTime getFirstDay(String unit) {
        LocalDateTime now = LocalDateTime.of(LocalDate.now(), LocalTime.MIN);
        LocalDateTime start;
        if ("w".equals(unit)) {
            start = now.with(DayOfWeek.MONDAY);
        } else if ("m".equals(unit)) {
            start = now.with(TemporalAdjusters.firstDayOfMonth());
        } else if ("y".equals(unit)) {
            start = now.with(TemporalAdjusters.firstDayOfYear());
        } else {
            start = now;
        }
        return start;
    }

    public static LocalDateTime getLastDay(String unit) {
        LocalDateTime now = LocalDateTime.of(LocalDate.now(), LocalTime.MAX);
        LocalDateTime end;
        if ("w".equals(unit)) {
            end = now.with(DayOfWeek.SUNDAY);
        } else if ("m".equals(unit)) {
            end = now.with(TemporalAdjusters.lastDayOfMonth());
        } else if ("y".equals(unit)) {
            end = now.with(TemporalAdjusters.lastDayOfYear());
        } else {
            end = now;
        }
        return end;
    }
}